package net.pws.common.security.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.pws.common.jackson.JsonUtils;
import net.pws.common.security.PrincipalManagerFactory;
import net.pws.common.security.SecurityContext;
import net.pws.common.security.WebUtils;
import net.pws.common.security.spi.Principal;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class PrincipalResolver extends AbstractAuthenticateFilter {
    
    public static final Log logger = LogFactory.getLog(PrincipalResolver.class);

    private static final String RPINCIPAL_KEY = "com.xlbs.common.security.filter#principal";
    
    private static final String FILTER_APPLIED = "__PrincipalResolver_filterApplied";
    
    private String[] anonymouseUrls = new String[] { "/login.html",
                                                    "/login.jsp" };
    
    public String[] getAnonymouseUrls() {
        return anonymouseUrls;
    }
    
    public void setAnonymouseUrls(String[] anonymouseUrls) {
        this.anonymouseUrls = anonymouseUrls;
    }
    
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain) throws IOException,
                                           ServletException {
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }
        
        if (!(request instanceof HttpServletRequest)) {
            throw new ServletException("Can only process HttpServletRequest");
        }
        
        if (!(response instanceof HttpServletResponse)) {
            throw new ServletException("Can only process HttpServletResponse");
        }
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        if (isAuthenticated(httpRequest, httpResponse) || isAnonymouseAccessAllowed(httpRequest,
                                                                                    httpResponse)) {
            try {
                chain.doFilter(request, response);
            }
            finally {
                SecurityContext.clear();
            }
            return;
        }
        
        String authToken = determineAuthTokenValue(httpRequest);
        if (logger.isDebugEnabled()) {
            logger.debug("Client Address:" + httpRequest.getRemoteAddr()
                         + ";"
                         + AUTH_TOKEN_KEY
                         + ":"
                         + authToken);
        }
        
        String uri = httpRequest.getRequestURI();
        
        if (StringUtils.isEmpty(authToken)) {
            if (uri.endsWith(".json")) {
                Map<String,Object> result = sendErrorJson("用户未登录！");
                WebUtils.renderJson(httpResponse, JsonUtils.toJson(result));
                return;
            }
            
            sendRedirect(httpRequest,
                         httpResponse,
                         computeLoginPage(httpRequest));
        }
        else {
            Principal principal = PrincipalManagerFactory.getInstance()
                                                         .get(authToken);
            // The princpal is timeout and removed from the cache
            if (principal == null) {
                if (uri.endsWith(".json")) {
                    Map<String,Object> result = sendErrorJson("用户登录已超时，请重新登录!");
                    WebUtils.renderJson(httpResponse, JsonUtils.toJson(result));
                    return;
                }
                
                sendRedirect(httpRequest,
                             httpResponse,
                             computeLoginPage(httpRequest));
                return;
            }
            
            SecurityContext.setContext(principal);
            request.setAttribute(RPINCIPAL_KEY,principal);
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            try {
                chain.doFilter(request, response);
            }
            finally {
                SecurityContext.clear();
            }
        }
    }
    
    private Map<String,Object> sendErrorJson(String message) {
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("succeed", Boolean.FALSE);
        result.put("statusCode", "401");
        result.put("message", message);
        return result;
    }
    
    private String computeLoginPage(HttpServletRequest request) {
        String referer = WebUtils.getRequestUrl(request);
        return getLoginPage() + "?page=" + referer;
    }
    
    private boolean isAuthenticated(HttpServletRequest request,
                                    HttpServletResponse response) {
        return (null != SecurityContext.getContext());
    }
    
    private boolean isAnonymouseAccessAllowed(HttpServletRequest request,
                                              HttpServletResponse response) {
        
        for (int i = 0; i < anonymouseUrls.length; i++) {
            if (matches(request, response, anonymouseUrls[i])) {
                return true;
            }
        }
        
        return false;
    }
    
}
